﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System.Threading.Tasks;
using System.IO;
using System.Configuration;

namespace DDMySqlBackupRestore
{
    class Program
    {
       
      
        private static bool ENABLE_SCHEDULER = false;
        public const string CUSTOM_DATE_FORMAT_SHORT = "dMMMyyyy";
        #region Sync constants
        private const string LOGS_CONTAINER_NAME_CONFIG = "com.dynamicdeploy.backuprestore.mysql.LogsContainer";
        private static string LOGS_CONTAINER_NAME = "ddmysqlbackuprestorelogs";
        #endregion
        #region constants for scheduler
        private const string ENABLE_SCHEDULER_CONFIG = "com.dynamicdeploy.backuprestore.mysql.EnableScheduler";
    

        internal const int TIMEINSECSFORINFINITEINITWAIT = 60;
        internal const int BACKUP_INITIAL_WAIT_MILLIS = 300000;
   

        private const string REBOOT_TRACKER_FILE_NAME = "ddreboot.tracker";
        private static string REBOOT_TRACKER_FILE_PATH = "ddreboot.tracker";
        public static bool IsReboot = false;

        public static string DEPLOYMENT_ID = null;
        public static string ROLE_ID = null;
        public static string INSTANCE_ID = null;

        internal const string MYSQLPROCESSNAME = "mysqld";

        public static string AppRoot = "";
        public static string RoleRoot = "";
        public static string SitesRoot = "";
        public static readonly RetryPolicy RETRY_POLICY = RetryPolicies.Retry(3, TimeSpan.Zero);

        const string LOG_FILE_NAME = "DDMySqlBackupRestore.log";

        #endregion
        static void Main(string[] args)
        {
            DefaultTraceListener drl;
            drl = (DefaultTraceListener)Trace.Listeners["Default"];
            drl.LogFileName = LOG_FILE_NAME;

            drl.TraceOutputOptions = TraceOptions.DateTime;
            Trace.TraceInformation("=====================================================================================================================================");

            Trace.TraceInformation(string.Format("{0}\t{1} in working directory {2}", DateTime.Now.ToString(), "Starting DDMySqlBackupRestore", Environment.CurrentDirectory));

            try
            {
                //if (RoleEnvironment.IsAvailable)
                //{
                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Start SetupConfigurationSettingPublisher()"));
                SetupConfigurationSettingPublisher();
                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "End SetupConfigurationSettingPublisher()"));
                if (!string.IsNullOrEmpty(GetConfigValueAsString(ENABLE_SCHEDULER_CONFIG)))
                {
                    ENABLE_SCHEDULER = bool.Parse(GetConfigValueAsString(ENABLE_SCHEDULER_CONFIG));

                    if (ENABLE_SCHEDULER)
                    {

                        // Register event handler for roleinstance stopping  and changed events
                        if (RoleEnvironment.IsAvailable)
                        {
                            RoleEnvironment.Stopping += new EventHandler<RoleEnvironmentStoppingEventArgs>(RoleEnvironment_Stopping);
                            RoleEnvironment.Changed += new EventHandler<RoleEnvironmentChangedEventArgs>(RoleEnvironment_Changed);

                            DEPLOYMENT_ID = RoleEnvironment.DeploymentId;
                            INSTANCE_ID = RoleEnvironment.CurrentRoleInstance.Id;
                            ROLE_ID = RoleEnvironment.CurrentRoleInstance.Role.Name;
                        }
                        else
                        {
                            DEPLOYMENT_ID = System.Guid.NewGuid().ToString("N");
                            INSTANCE_ID = "1";
                            ROLE_ID = Environment.MachineName;
                        }

                        bool fileCreated;
                        REBOOT_TRACKER_FILE_PATH = FileUtils.CreateRebootTrackerFile(REBOOT_TRACKER_FILE_NAME, out fileCreated);
                        IsReboot = !fileCreated;

                        FillEnvironmentVariables();

                        InitScheduler();


                        Task t = new Task(() =>
                        {
                            BatchScriptsExecutor.GenerateTimersFromConfiguration(3);
                        });

                        try
                        {
                            t.Start();

                        }
                        catch (Exception ex)
                        {
                            Trace.TraceError("Error initializing batch schedulers. " + ex.Message);
                        }

                        System.Threading.Tasks.Task.Factory.StartNew(() =>
                        {
                            try
                            {
                                while (true)
                                {

                                    Trace.TraceInformation("Starting log file upload thread.");
                                    Thread.Sleep(TimeSpan.FromSeconds(TIMEINSECSFORINFINITEINITWAIT * 120));
                                    UploadLogFile();
                                }
                            }
                            catch (Exception ex)
                            {

                                Trace.TraceInformation("Error uploading log file " + ex.Message);
                            }
                        });




                        while (true)
                        {
                            Thread.Sleep(TimeSpan.FromSeconds(300));


                        }

                    }
                    else
                    {

                        Trace.TraceInformation("Backup and Restore schedules are disabled. Quitting");

                        Environment.Exit(-1);
                       
                    }

                }

            }
            catch (Exception ex)
            {

                Trace.TraceError("Error in application. Quitting.. " + ex.Message);

            }
        }

        
        #region Role Environment
        private static void SetupConfigurationSettingPublisher()
        {
            // This code sets up a handler to update CloudStorageAccount instances when their corresponding
            // configuration settings change in the service configuration file.
            Trace.TraceInformation("Setting up configuration setting publishing");
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                // Provide the configSetter with the initial value
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));

                RoleEnvironment.Changed += (sender, arg) =>
                {
                    if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
                        .Any((change) => (change.ConfigurationSettingName == configName)))
                    {
                        // The corresponding configuration setting has changed, propagate the value
                        if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
                        {
                            // In this case, the change to the storage account credentials in the
                            // service configuration is significant enough that the role needs to be
                            // recycled in order to use the latest settings. (for example, the 
                            // endpoint has changed)
                            RoleEnvironment.RequestRecycle();
                        }
                    }
                };
            });
        }
        static void RoleEnvironment_Changed(object sender, RoleEnvironmentChangedEventArgs e)
        {
            try
            {
                Trace.TraceError("Roleinstance changed. Will not change synchronization type. Please reboot for changing the synchronization type.");
                if (e.Changes.Any(chg => chg is RoleEnvironmentConfigurationSettingChange))
                {
                    // Perform an action, for example, you can initialize a client, 
                    // or you can recycle the role
                    Trace.TraceError("Any change to configuration will trigger a recycle to maintain consistency");
                    RoleEnvironment.RequestRecycle();
                }

                //// Get the list of configuration changes
                //var settingChanges = e.Changes.OfType<RoleEnvironmentConfigurationSettingChange>();

                //foreach (var settingChange in settingChanges)
                //{
                //    if( settingChange.ConfigurationSettingName == 
                //    Trace.WriteLine(message, "Information");
                //}
            }
            catch (Exception ex)
            {

                Trace.TraceError("Error processing setting change." + ex.Message);

            }

        }

        static void RoleEnvironment_Stopping(object sender, RoleEnvironmentStoppingEventArgs e)
        {
            try
            {
                Trace.TraceWarning("Roleinstance stopping, hence terminating file synchronization.");


                Task t1 = new Task(() =>
                {
                   

                    Trace.TraceWarning("Running backup before stopping the instance.");
                    InitializeMySqlScheduler(false, true);
                 
                    Trace.TraceWarning("Ran backup before stopping the instance.");

                });

                 

                  t1.Start();
                 

                  Task.WaitAll(new Task[] {t1}, TimeSpan.FromMinutes(15));
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error in RoleEnvironment_Stopping " + ex.Message);
            }
            finally
            {

                UploadLogFile();
            }
            Environment.Exit(-1);
        }
        #endregion

        #region Logs
        static void UploadLogFile()
        {

            try
            {

                string filePath = Path.Combine(Environment.CurrentDirectory, LOG_FILE_NAME);
                if (File.Exists(filePath) && new FileInfo(filePath).Length > 0)
                {
                    string logsContainer = LOGS_CONTAINER_NAME;
                    if (!string.IsNullOrEmpty(GetConfigValueAsString("com.dynamicdeploy.backuprestore.mysql.LogsContainer")))
                    {
                        logsContainer = GetConfigValueAsString("com.dynamicdeploy.backuprestore.mysql.LogsContainer");
                    }
                    var logs = CloudStorageAccount.Parse(GetConfigValueAsString("com.dynamicdeploy.backuprestore.mysql.DataConnectionString")).CreateCloudBlobClient().GetContainerReference(logsContainer);
                    logs.CreateIfNotExist();
                    string customDT = DateTime.UtcNow.ToString(CUSTOM_DATE_FORMAT_SHORT).Replace(" ", "");
                    var error = logs.GetBlobReference(string.Format("{0}/{1}/{2}/{3}{4}.log", DEPLOYMENT_ID, RoleEnvironment.CurrentRoleInstance.Id, customDT, "DDMySqlBackupRestore-", (DateTime.MaxValue - DateTime.UtcNow).Ticks.ToString("d19")));
                    error.Properties.ContentType = "text/plain";


                    Trace.TraceInformation("=====================================================================================================================================");

                    error.UploadFile(filePath, new BlobRequestOptions() { RetryPolicy = RETRY_POLICY, Timeout = TimeSpan.FromSeconds(60) });
                    File.WriteAllText(filePath, string.Empty);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error uploading log file " + ex.Message);
            }
        }
        #endregion

        #region Backup Restore Scheduler

        private static void InitScheduler()
        {
            System.Threading.Tasks.Task t = new System.Threading.Tasks.Task(() =>
            {
               
                while (true)
                {
                    try
                    {

                        Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Initializing MySqlBackupServices()"));
                        if (InitializeMySqlScheduler(true, false))
                        {
                            Trace.TraceInformation("Initialized MySqlBackupServices. Ready to go!");
                            return;
                        }
                        else
                        {
                            Trace.TraceError("There was an issue initializing the MySql backup scheduler. Will go in an infinite loop till directories are available. Will wait " + TIMEINSECSFORINFINITEINITWAIT + "secs");
                            Thread.Sleep(TimeSpan.FromSeconds(TIMEINSECSFORINFINITEINITWAIT));
                            UploadLogFile();

                        }
                        Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Initialized MySqlBackupServices()"));


                    }
                    catch (Exception ex)
                    {

                        Trace.TraceInformation("General exception whil starting MySqlSchedule task " + ex.Message);

                    }

                }

            });

            try
            {
                t.Start();

            }
            catch (Exception ex)
            {

                Trace.TraceInformation("General exception whil starting MySqlSchedule task " + ex.Message);


            }

        }
        private static MySqlBackupTrigger GetTriggerFromString(string configString)
        {
            string comStr = configString.ToLower();

            if (comStr == MySqlBackupTrigger.daily.ToString().ToLower())
            {
                return MySqlBackupTrigger.daily;

            }
            else if (comStr == MySqlBackupTrigger.halfday.ToString())
            {
                return MySqlBackupTrigger.halfday;

            }
            else if (comStr == MySqlBackupTrigger.halfhour.ToString())
            {
                return MySqlBackupTrigger.halfhour;

            }
            else if (comStr == MySqlBackupTrigger.hourly.ToString())
            {
                return MySqlBackupTrigger.hourly;

            }
            else if (comStr == MySqlBackupTrigger.monthly.ToString())
            {
                return MySqlBackupTrigger.monthly;

            }

            return MySqlBackupTrigger.daily;
        }

      

    
        private static bool InitializeMySqlScheduler(bool runRestore, bool runNow)
        {
            Trace.TraceInformation("Starting scheduler.");
            try
            {
               
                        while (true)
                        {
                            if (FileUtils.IsProcessRunning(MYSQLPROCESSNAME))
                            {
                                Trace.TraceInformation("MySQL is ready.");
                                break;
                            }
                            else
                            {
                                Trace.TraceInformation("MySql has not started yet. Will go in an infinite loop till directories are available. Will wait " + TIMEINSECSFORINFINITEINITWAIT + "secs");
                                Thread.Sleep(TimeSpan.FromSeconds(TIMEINSECSFORINFINITEINITWAIT));
                                continue;
                            }

                        }


                        return MySqlBackupRestoreConfig.ScheduleAndGetConfigObjectsFromConfiguration(runRestore: runRestore, runNow:runNow);
                       
                



            }
            catch (Exception ex)
            {

                 Trace.TraceError("General error in initializing MySqlBackup Scheduler " + ex.Message);

                return false;
            }

             
           
        }
        #endregion

        
        #region Utility Functions
        public static string GetConfigValueAsString(string configName)
        {
            return (RoleEnvironment.IsAvailable)
                                   ? RoleEnvironment.GetConfigurationSettingValue(configName)
                                   : ConfigurationManager.AppSettings[configName];

        }

        public static string ExpandKeywords(string buffer)
        {
            if (RoleEnvironment.IsAvailable)
            {
                buffer = buffer.Replace("$approot$", AppRoot);
                buffer = buffer.Replace("$deploymentid$", RoleEnvironment.DeploymentId);
                buffer = buffer.Replace("$roleinstanceid$", RoleEnvironment.CurrentRoleInstance.Id);
                buffer = buffer.Replace("$computername$", Environment.MachineName);
                buffer = buffer.Replace("$guid$", Guid.NewGuid().ToString());
                buffer = buffer.Replace("$now$", DateTime.Now.ToString());
                buffer = buffer.Replace("$roleroot$", Environment.GetEnvironmentVariable("RoleRoot"));

                if (!string.IsNullOrEmpty(SitesRoot))
                {

                    buffer = buffer.Replace("$sitesroot$", SitesRoot);
                }
            }
            return buffer;
        }
        public static void FillEnvironmentVariables()
        {
            if (RoleEnvironment.IsAvailable)
            {
                try
                {
                    RoleRoot = Environment.GetEnvironmentVariable("RoleRoot");

                    AppRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\approot";
                    SitesRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\sitesroot";
                    if (!Directory.Exists(SitesRoot))
                    {
                        SitesRoot = null;
                        // May be WorkerRole
                        //AppRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\approot";
                        //SitesRoot = AppRoot;
                        if (RoleEnvironment.IsEmulated)
                        {

                            SitesRoot = AppRoot;
                        }
                    }

                    Trace.TraceInformation("Sites Root " + ((!string.IsNullOrEmpty(SitesRoot)) ? SitesRoot : "Seems to be a Worker Role"));
                    Trace.TraceInformation("AppRoot " + AppRoot);


                }
                catch (Exception ex)
                {
                    Trace.TraceError("Error retrieving environment variables and config variables in FillEnvironmentVariables() " + ex.Message);

                }
            }
        }

        #endregion
    }
}
